﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Reflection;
using System.Collections.Generic;
using System.ComponentModel;

using Microsoft.Practices.Unity;
using Microsoft.Practices.Composite.Events;
using Microsoft.Practices.Composite.Presentation.Events;
using Might.Infrastructure.Annotations.Graphics;
using Might.Infrastructure.Events;
using Might.Infrastructure.Services;
using Might.UIControls.PropertyEditor.Items;

namespace Might.UIControls.PropertyEditor {
    /// <summary>
    /// This class manage funcionality of property editor. Can works with different views.
    /// </summary>
    /// <remarks>
    /// If you want to add new type of item to the property editor, you have to edit these functions :
    /// <see cref="addPropertyToView"/> , <see cref="isSameValue"/> , <see cref="setValueToGraphicsPrimitive"/> , 
    /// <see cref="setValueToPropertyEditorItem"/> .
    /// </remarks>
    public class PropertyEditorPresenter {
        private IGraphicsItem selectedPrimitive; //reference on selected primitive in graphics editor

        private List<IGraphicsItem> multipleSelectedPrimitives = new List<IGraphicsItem>();
                                    //list of selected primitives in graphics editor

        private bool multipleSelection; //indicates if current selection is single or multiple        

        private readonly Dictionary<string, IPropertyEditorItem> propertyEditorItems =
            new Dictionary<string, IPropertyEditorItem>(); //items in property editor

        private IPropertyEditorView view;
        private readonly IUnityContainer container;

        ///<summary>
        ///</summary>
        ///<param name="container"></param>
        ///<param name="eventAggregator"></param>
        public PropertyEditorPresenter(IUnityContainer container, IEventAggregator eventAggregator) {
            eventAggregator.GetEvent<GraphicsPrimitiveSelectedEvent>().Subscribe(
                GraphicsPrimitiveSelectedEventHandler, ThreadOption.UIThread, false);
            eventAggregator.GetEvent<GraphicsPrimitiveMultipleSelectedEvent>().Subscribe(
                GraphicsPrimitiveMultipleSelectedEventHandler, ThreadOption.UIThread, false);
            eventAggregator.GetEvent<GraphicsPrimitiveMultipleChangedEvent>().Subscribe(
                GraphicsPrimitiveMultipleChangedEventHandler, ThreadOption.UIThread, false);
            this.container = container;
        }

        /// <summary>
        /// Gets or sets the view.
        /// </summary>
        /// <value>The view.</value>
        public IPropertyEditorView View {
            get { return view; }
            set {
                view = value;
                value.ItemChanged += propertyEditor_ItemChanged;
            }
        }

        /// <summary>
        /// Event handler invoked when one primitive in graphics editor is selected.
        /// </summary>
        /// <param name="prim">The graphics primitive</param>
        public void GraphicsPrimitiveSelectedEventHandler(IGraphicsItem prim) {
            container.Resolve<IMightLogger>().Log(this, "single primitive selected");

            if (selectedPrimitive != prim) {
                clearPreviousSelection();
                multipleSelection = false;

                selectedPrimitive = prim;
                selectedPrimitive.Changed += primitive_GraphicsPrimitiveChanged;

                addPropertyEditorItems();
            }
        }

        /// <summary>
        /// Event handler invoked when more primitives in graphics editor are selected.
        /// </summary>
        /// <param name="primitives">The primitives from multiple selection</param>
        public void GraphicsPrimitiveMultipleSelectedEventHandler(List<IGraphicsItem> primitives) {
            container.Resolve<IMightLogger>().Log(this, "multiple selected");

            if (primitives != null && primitives.Count > 0) {
                if (primitives.Count == 1) {
                    GraphicsPrimitiveSelectedEventHandler(primitives[0]);
                } else {
                    clearPreviousSelection();
                    multipleSelection = true;

                    multipleSelectedPrimitives = primitives;

                    addPropertyEditorItemsFromMultipleSelection();
                }
            }
        }

        /// <summary>
        /// Event handler invoked when selection with more primitives are changed (position, size etc...)
        /// </summary>
        public void GraphicsPrimitiveMultipleChangedEventHandler(object obj) {
            container.Resolve<IMightLogger>().Log(this, "multiple changed");
            changeValueInView();
        }

        /// <summary>
        /// Handles the GraphicsPrimitiveChanged event of the primitive control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void primitive_GraphicsPrimitiveChanged(object sender, EventArgs e) {
            container.Resolve<IMightLogger>().Log(this, "single primitive changed");
            changeValueInView();
        }

        /// <summary>
        /// Updates items in property editor.
        /// </summary>
        private void changeValueInView() {
            IGraphicsItem primitive;

            primitive = multipleSelection ? multipleSelectedPrimitives[0] : selectedPrimitive;

            Type t = primitive.GetType();

            foreach (var item in propertyEditorItems) {
                if (multipleSelection) {
                    if (isSameValue(t.GetProperty(item.Key))) {
                        setValueToPropertyEditorItem(item.Value, primitive, true);
                    } else {
                        setValueToPropertyEditorItem(item.Value, primitive, false);
                    }
                } else {
                    setValueToPropertyEditorItem(item.Value, primitive, true);
                }
            }
        }

        /// <summary>
        /// Adds items to the property editor according of the fields in selected primitive.
        /// </summary>
        private void addPropertyEditorItems() {
            List<PropertyInfo> properties = analyzePrimitive(selectedPrimitive);

            var roItem = new ReadOnlyItem("Object type") {Value = selectedPrimitive.GetType().Name};
            View.AddItem(null, roItem);

            foreach (PropertyInfo property in properties) {
                addPropertyToView(property, selectedPrimitive, true);
            }
        }

        /// <summary>
        /// Analyzes the fields in graphics primitive.
        /// </summary>
        /// <returns></returns>
        private static List<PropertyInfo> analyzePrimitive(IGraphicsItem primitive) {
            Type[] interfaces = primitive.GetType().GetInterfaces();

            var interfacesProperties = new List<PropertyInfo>();
            foreach (Type t in interfaces) {
                interfacesProperties.AddRange(t.GetProperties());
            }

            return interfacesProperties;
        }

        private void addPropertyToView(PropertyInfo property, IGraphicsItem primitive, bool getValueFromPrimitive) {
            IPropertyEditorItem item = null;

            if (property.PropertyType == typeof (double)) {
                item = new PositiveDoubleItem(property.Name);
            } else if (property.PropertyType == typeof (Brush)) {
                var br = (Brush)property.GetValue(primitive, null);

                if (br is SolidColorBrush) {
                    item = new ColorComboBoxItem(property.Name);
                }
            } else if (property.PropertyType == typeof (bool)) {
                item = new BoolItem(property.Name);
            } else if (property.PropertyType.IsEnum) {
                item = new EnumItem(property.Name, property.PropertyType);
            }

            if (item != null) {
                object[] attributes = property.GetCustomAttributes(true);
                string groupName = null;
                foreach (object attr in attributes) {
                    if (attr is CategoryAttribute) {
                        groupName = (attr as CategoryAttribute).Category;
                        break;
                    }
                }
                
                if( groupName != null ) {

                    setValueToPropertyEditorItem(item, primitive, getValueFromPrimitive);
                    view.AddItem(groupName, item);
                    propertyEditorItems.Add(property.Name, item);
                    
                }
                
            }
        }

        /// <summary>
        /// Adds items to the property editor from multiple selection.
        /// </summary>
        private void addPropertyEditorItemsFromMultipleSelection() {
            var roItem = new ReadOnlyItem("Object type") {Value = "multiple selection"};
            View.AddItem(null, roItem);

            foreach (IGraphicsItem primitive in multipleSelectedPrimitives) {
                List<PropertyInfo> properties = analyzePrimitive(primitive);

                foreach (PropertyInfo property in properties) {
                    //testing if property is not added to the property editor
                    if (!propertyEditorItems.ContainsKey(property.Name)) {
                        //testing if this property is in all primitives in multiple selection
                        if (isCommonProperty(property)) {
                            //testing if properies have same value
                            if (isSameValue(property)) {
                                addPropertyToView(property, primitive, true);
                            } else {
                                addPropertyToView(property, primitive, false);
                            }
                        }
                    }
                }
            }
        }


        /// <summary>
        /// Determines whether property exists in all primitives in multiple selection.
        /// </summary>
        /// <param name="property">The property.</param>
        /// <returns>
        /// 	<c>true</c> if property exists in all primitives in multiple selection; otherwise, <c>false</c>.
        /// </returns>
        private bool isCommonProperty(PropertyInfo property) {
            foreach (IGraphicsItem primitive in multipleSelectedPrimitives) {
                if (primitive.GetType().GetProperty(property.Name) == null) {
                    return false;
                }
                if (primitive.GetType().GetProperty(property.Name).PropertyType != property.PropertyType) {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Determines whether property has same value in all primitives in multiple selection.
        /// </summary>
        /// <param name="property">The property.</param>
        /// <returns>
        /// 	<c>true</c> if property has same value in all primitive in multiple selection; otherwise, <c>false</c>.
        /// </returns>
        /// <remarks>Function suppose, that property exists in all primitives in multiple selection. See <see cref="isCommonProperty"/></remarks>
        private bool isSameValue(PropertyInfo property) {
            if (property.PropertyType == typeof (double)) {
                var value1 = (double)multipleSelectedPrimitives[0].GetType().GetProperty(property.Name)
                                         .GetValue(multipleSelectedPrimitives[0], null);

                for (int i = 1; i < multipleSelectedPrimitives.Count; i++) {
                    var value2 = (double)multipleSelectedPrimitives[i].GetType().GetProperty(property.Name)
                                             .GetValue(multipleSelectedPrimitives[i], null);

                    if (value1 != value2) {
                        return false;
                    }
                }
            } else if (property.PropertyType == typeof (Brush)) {
                var br1 = (Brush)multipleSelectedPrimitives[0].GetType().GetProperty(property.Name)
                                     .GetValue(multipleSelectedPrimitives[0], null);

                for (int i = 1; i < multipleSelectedPrimitives.Count; i++) {
                    var br2 = (Brush)multipleSelectedPrimitives[i].GetType().GetProperty(property.Name)
                                         .GetValue(multipleSelectedPrimitives[i], null);

                    if (br1 is SolidColorBrush && br2 is SolidColorBrush) {
                        if ((br1 as SolidColorBrush).Color != (br2 as SolidColorBrush).Color) {
                            return false;
                        }
                    } else {
                        return false;
                    }
                }
            } else if (property.PropertyType == typeof (bool)) {
                var value1 = (bool)multipleSelectedPrimitives[0].GetType().GetProperty(property.Name)
                                       .GetValue(multipleSelectedPrimitives[0], null);

                for (int i = 1; i < multipleSelectedPrimitives.Count; i++) {
                    var value2 = (bool)multipleSelectedPrimitives[i].GetType().GetProperty(property.Name)
                                           .GetValue(multipleSelectedPrimitives[i], null);

                    if (value1 != value2) {
                        return false;
                    }
                }
            } else if (property.PropertyType.IsEnum) {
                var value1 = (Enum)multipleSelectedPrimitives[0].GetType().GetProperty(property.Name)
                                       .GetValue(multipleSelectedPrimitives[0], null);

                for (int i = 1; i < multipleSelectedPrimitives.Count; i++) {
                    var value2 = (Enum)multipleSelectedPrimitives[i].GetType().GetProperty(property.Name)
                                           .GetValue(multipleSelectedPrimitives[i], null);

                    if (value1 != value2) {
                        return false;
                    }
                }
            }

            return true;
        }


        /// <summary>
        /// Clears data from the previous selection.
        /// </summary>
        private void clearPreviousSelection() {
            propertyEditorItems.Clear();
            View.ClearItems();

            if (selectedPrimitive != null) {
                selectedPrimitive.Changed -= primitive_GraphicsPrimitiveChanged;
            }
            multipleSelectedPrimitives.Clear();
        }

        /// <summary>
        /// Handles the ItemChanged event of the property editor control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void propertyEditor_ItemChanged(object sender, EventArgs e) {
            if (multipleSelection) {
                foreach (IGraphicsItem primitive in multipleSelectedPrimitives) {
                    setValueToGraphicsPrimitive(sender as IPropertyEditorItem, primitive);
                }
            } else {
                setValueToGraphicsPrimitive(sender as IPropertyEditorItem, selectedPrimitive);
            }
        }

        /// <summary>
        /// Sets the value changed in the property editor control to the graphics primitive.
        /// </summary>
        /// <param name="item">The item in property editor which contains changed data.</param>
        /// <param name="primitive">The graphics primitive.</param>
        private void setValueToGraphicsPrimitive(IPropertyEditorItem item, IGraphicsItem primitive) {
            if (item is PositiveDoubleItem) {
                if (multipleSelection) {
                    double? value = (item as PositiveDoubleItem).Value;
                    if (value.HasValue) {
                        primitive.GetType().GetProperty(item.Label).SetValue(primitive, value.Value, null);
                    }
                } else {
                    primitive.GetType().GetProperty(item.Label).SetValue(
                        primitive, (item as PositiveDoubleItem).Value, null);
                }
            } else if (item is ColorComboBoxItem) {
                if (multipleSelection) {
                    Color? value = (item as ColorComboBoxItem).Value;
                    if (value.HasValue) {
                        primitive.GetType().GetProperty(item.Label).SetValue(
                            primitive, new SolidColorBrush(value.Value), null);
                    }
                } else {
                    primitive.GetType().GetProperty(item.Label).SetValue(
                        primitive, new SolidColorBrush((item as ColorComboBoxItem).Value.Value), null);
                }
            } else if (item is BoolItem) {
                if (multipleSelection) {
                    bool? value = (item as BoolItem).Value;
                    if (value.HasValue) {
                        primitive.GetType().GetProperty(item.Label).SetValue(primitive, value.Value, null);
                    }
                } else {
                    primitive.GetType().GetProperty(item.Label).SetValue(primitive, (item as BoolItem).Value, null);
                }
            } else if (item is EnumItem) {
                if (multipleSelection) {
                    Enum value = (item as EnumItem).Value;
                    if (value != null) {
                        primitive.GetType().GetProperty(item.Label).SetValue(primitive, value, null);
                    }
                } else {
                    primitive.GetType().GetProperty(item.Label).SetValue(primitive, (item as EnumItem).Value, null);
                }
            }
        }

        /// <summary>
        /// Sets the value to the property editor item.
        /// </summary>
        /// <param name="item">The property editor item.</param>
        /// <param name="primitive">The primitive which contains the value</param>
        /// <param name="getValueFromPrimitive">if set to <c>true</c> then value is got from primitive parameter.</param>
        private static void setValueToPropertyEditorItem(
            IPropertyEditorItem item, IGraphicsItem primitive, bool getValueFromPrimitive) {
            PropertyInfo property = primitive.GetType().GetProperty(item.Label);

            if (item is PositiveDoubleItem) {
                if (getValueFromPrimitive) {
                    (item as PositiveDoubleItem).Value = (double)property.GetValue(primitive, null);
                } else {
                    (item as PositiveDoubleItem).Value = null;
                }
            } else if (item is ColorComboBoxItem) {
                var br = (Brush)property.GetValue(primitive, null);
                if (br is SolidColorBrush) {
                    if (getValueFromPrimitive) {
                        (item as ColorComboBoxItem).Value = (br as SolidColorBrush).Color;
                    } else {
                        (item as ColorComboBoxItem).Value = null;
                    }
                }
            } else if (item is BoolItem) {
                if (getValueFromPrimitive) {
                    (item as BoolItem).Value = (bool)property.GetValue(primitive, null);
                } else {
                    (item as BoolItem).Value = null;
                }
            } else if (item is EnumItem) {
                if (getValueFromPrimitive) {
                    (item as EnumItem).Value = property.GetValue(primitive, null) as Enum;
                } else {
                    (item as EnumItem).Value = null;
                }
            }
        }
    }
}

